package com.chaoxinet.csmall.passport.filter;

import com.alibaba.fastjson.JSON;
import com.chaoxinet.csmall.passport.security.LoginPrincipal;
import com.chaoxinet.csmall.passport.web.JsonResult;
import com.chaoxinet.csmall.passport.web.ServiceCode;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

/**
 * @Name: JwtAuthorzationFilter
 * @Author: 西风残叶
 * @Company: 朝兮网络工作室
 * @Date: 2024/2/12 10:18
 * @TODO: 处理JWT的过滤器类
 */

@Slf4j
@Component
public class JwtAuthorzationFilter extends OncePerRequestFilter {
    // 在配置文件中配置了secretKey
    @Value("${camall.jwt.secret-key}")
    private String secretKey;

    /**
     * <p>处理JWT的过滤器类</p>
     *
     * <p>此过滤器类的主要职责:</p>
     * <ul>
     *     <li>接受客户端可能提交的JWT</li>
     *     <li>尝试解析客户端提交的JWT</li>
     *     <li>将解析的得到的结果存入到SecurityContext中</li>
     * </ul>
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 清除SecurityContext中的数据
        SecurityContextHolder.clearContext();
        // 根据业内惯用的做法,客户端应该将JWT保存在请求头(Request Header)中的名为Authentication的属性中
        String jwt = request.getHeader("Authorization");
        log.debug("尝试接收客户端携带的JWT数据,JWT:{}", jwt);
        //判断是否提交了有效的jwt
        if (!StringUtils.hasText(jwt) || jwt.length() < 113) {
            // 直接放行
            filterChain.doFilter(request, response);
            // [重要]终止当前方法的执行,不执行当前方法接下来的代码
            return;
        }
        // 尝试解析JWT
        // [已解决]问题1:SecretKey不应该被重复定义,应该写在配置文件中
        // [已解决]问题2:解析过程中可能抛出异常
        Claims claims = null;
        response.setContentType("application/json;charset=utf-8");
        try {
            // 解析JWT
            claims = Jwts.parser()
                    .setSigningKey(secretKey)  // 设置签名密钥
                    .parseClaimsJws(jwt)  // 解析JWT令牌
                    .getBody();  // 获取JWT令牌中的声明
        } catch (SignatureException e) {
            // 处理签名异常
            String message = "非法访问!";
            log.warn("解析JWT时出现SignatureException,响应消息:{}", message);
            JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_SIGNATURE, message);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(JSON.toJSONString(jsonResult));
            printWriter.close();
            return;
        } catch (MalformedJwtException e) {
            // 捕获JWT格式错误异常，提示非法访问
            String message = "非法访问!";
            log.warn("解析JWT时出现MalformedJwtException,响应消息:{}", message);
            JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_MALFORMED, message);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(JSON.toJSONString(jsonResult));
            printWriter.close();
            return;
        } catch (ExpiredJwtException e) {
            // 捕获JWT过期异常，提示登录已过期
            String message = "您的登录已过期,请重新登录!";
            log.warn("解析JWT时出现ExpiredJwtException,响应消息:{}", message);
            JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_EXPIRED, message);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(JSON.toJSONString(jsonResult));
            printWriter.close();
            return;
        }
        // 获取声明中的id和username
        Long id = claims.get("id",Long.class);
        String username = claims.get("username",String.class);
        String authoritiesJsonString = claims.get("authoritiesJsonString", String.class);
        log.debug("从JWT中解析出来的管理员ID:{}", id);
        log.debug("从JWT中解析出来的管理员用户名:{}", username);
        log.debug("从JWT中解析出来的管理员权限列表的JSON:{}", authoritiesJsonString);

        // 将JSON格式的权限列表转换成Authentication需要的类型(Collection<GrantedAuthority>)
        List<SimpleGrantedAuthority> authorities =
                JSON.parseArray(authoritiesJsonString, SimpleGrantedAuthority.class);
        // 基于解析JWT的结果创建认证信息
        // [已解决]问题3:是否应该使用"用户名"这么简单的数据作为"当事人"?
        // 问题4:如何得到当前管理员的权限
        LoginPrincipal principal = new LoginPrincipal();
        principal.setId(id);
        principal.setUsername(username);
        Object credentials = null; // 应该为null
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
        // 将认证信息存入到SecurityContext中
        SecurityContext context = SecurityContextHolder.getContext();
        context.setAuthentication(authentication);
        // 过滤器继续向后执行,即:放行
        // 如果没有执行以下代码,表示:"阻止",即使请求的处理过程到此结束,在浏览器中将显示一片空白
        filterChain.doFilter(request, response);
    }
}